import java.net.*;
import java.util.StringTokenizer;
import java.io.*;

/**
 * A server which will wait for a connection from a client. When the client closes 
 * the connection, the server is ready for the next client.
 */

public class GeneralServer {

    protected int portNumber = 40308;
    public static String serverAddress;
    protected ServerSocket serverSocket;

    /**
     * Will print out the IP address of the local host and the port on which this
     * server is accepting connections. 
     */
    protected void printAndSetServerAddress() {
        try {
            InetAddress localhost = InetAddress.getLocalHost();
            String localhostAddress = localhost.getHostAddress();
            System.out.println("Contact this server on the IP address " + localhostAddress + " port: " + portNumber);
            serverAddress = localhostAddress;
        } catch (UnknownHostException e) {
            System.err.println("Cannot resolve the Internet address of the local host.");
            System.err.println(e);
            System.exit(-1);
        }
    }

    /**
     * Will register this server on the port number portNumber. Will not start waiting
     * for connections. For this you should call waitForConnectionFromClient().
     */
    protected void registerOnPort() {
        try {
            serverSocket = new ServerSocket(portNumber);
        } catch (IOException e) {
            serverSocket = null;
            System.err.println("Cannot open server socket on port number" + portNumber);
            System.err.println(e);
            System.exit(-1);
        }
    }

    public void deRegisterOnPort() {
        if (serverSocket != null) {
            try {
                serverSocket.close();
                serverSocket = null;
            } catch (IOException e) {
                System.err.println(e);
            }
        }
    }

    /**
     * Waits for the next client to connect on port number portNumber or takes the 
     * next one in line in case a client is already trying to connect. Returns the
     * socket of the connection, null if there were any failures.
     */
    protected Socket waitForConnectionFromClient() {
        Socket res = null;
        try {
            res = serverSocket.accept();
        } catch (IOException e) {
            // We return null on IOExceptions
        }
        return res;
    }

    private void safetyCheck(String url) {
        //TODO implement some kind of check of the url to prevent unwanted access
    }

    public void processError(){
        //TODO Unsupported operation
    }

    public void run(Server server) {
        printAndSetServerAddress();
        registerOnPort();

        while (true) {
            Socket socket = waitForConnectionFromClient();

            if (socket != null) { //A client has connected
                System.out.println("Connection from " + socket);
                try {
                    //We try to read what the client sends us.
                    BufferedReader fromClient = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                    //We process the header, ignoring the cases where it is a response.
                    {
                        String requestLine = fromClient.readLine();
                        if(requestLine != null){
                            StringTokenizer tokenizer = new StringTokenizer(requestLine);

                            switch(tokenizer.countTokens()){
                                case 2: //Simple request?
                                    if(tokenizer.nextToken().equals("GET")){
                                        String URL = tokenizer.nextToken();
                                        safetyCheck(URL);
                                        server.processSimpleGET(socket, URL); //try to handle as simple GET
                                    }
                                    else{
                                        processError();
                                    }
                                    break;

                                case 3: //full request?
                                    commandType requestType;

                                    //find type of the request
                                    String requestTypeString = tokenizer.nextToken();
                                    if(requestTypeString.equals("GET")) requestType = commandType.GET;
                                    else if(requestTypeString.equals("POST")) requestType = commandType.POST;
                                    else if(requestTypeString.equals("PUT")) requestType = commandType.PUT;
                                    else {
                                        //Unsupported request type, ignore the request
                                        break;
                                    }

                                    //read the URL of the String
                                    String URL = tokenizer.nextToken();
                                    safetyCheck(URL);

                                    if(tokenizer.nextToken().equals("HTTP/1.0") || true){ //We're being a bit large for testing purposes
                                        //Valid request

                                        //Read the rest of the request fields, currently ignoring them.
                                        String requestField;
                                        while ( (requestField = fromClient.readLine()) != null) {
                                            if( requestField.equals("") ) break; //end of request
                                        }

                                        switch (requestType) {
                                            case GET:
                                                server.processFullGET(socket, URL);
                                                break;
                                            case POST:
                                                server.processPOST(socket, URL);
                                                break;
                                            case PUT:
                                                server.processPUT(socket, URL);
                                                break;
                                        }
                                    }
                                    break;

                                default: //bad request
                                    processError();
                                    break;
                            }
                        }
                    }
                    fromClient.close();
                    socket.close();

                } catch (IOException e) {
                    // We report but otherwise ignore IOExceptions
                    System.err.println(e);
                }
                System.out.println("Connection closed by client.");
            }else {
                // We rather aggressively terminate the server on the first connection exception
                break;
            }
        }

        deRegisterOnPort();
    }

    public static void main(String[] args) throws IOException {
        //Run the server
        GeneralServer gServer = new GeneralServer();
        gServer.run(new BankServer());
    }

    private enum commandType {
        POST, GET, PUT
    }

}
